package MonteCarlo;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

import Interface.Interface;

public class MonteCarlo implements Runnable {

	/********************************************/
	/**************** ATTRIBUTES ****************/
	/********************************************/
	/**
	 * Attributes useful for the computation of a simulation
	 */
	private Double S;
	private Double X;
	private Double Drift;
	private Double vSqrdt;
	private Integer z;
	private Double r;
	private Double T;
	private Integer nSteps;

	/**
	 * Attributes useful for several simulations
	 */
	private Integer nSimulations;
	private Double Sum;

	/**
	 * Attributes related to Threads and CPU consumption
	 */
	private Integer nThreads;
	private long TimeTaken;

	/********************************************/
	/**************** FUNCTIONS *****************/
	/********************************************/
	/**
	 * Default constructor (load default value from config.txt)
	 */
	public MonteCarlo() {
		Drift = 0.0;
		vSqrdt = 0.0;
		Sum = 0.0;
		z = 0;
		TimeTaken = 0;
		setStandardOption("config.txt", 1);
	}

	/**
	 * Define the set of argument to use from a file
	 * 
	 * @param configPath
	 *            the config file to be used
	 * @param nThreads
	 *            the number of threads to use (0 for auto-optimal threads
	 *            number)
	 */
	public void setStandardOption(String configPath, Integer nThreads) {

		String CallPutFlag = "c";
		Double b = 0.0, v = 0.0;
		Double dt = 0.0;

		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(configPath));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		String line;
		try {
			while ((line = reader.readLine()) != null) {
				if (!line.startsWith("#") && !line.isEmpty()) {
					if (line.startsWith("CallPutFlag="))
						CallPutFlag = line.substring(12);
					else if (line.startsWith("S="))
						S = Double.parseDouble(line.substring(2));
					else if (line.startsWith("X="))
						X = Double.parseDouble(line.substring(2));
					else if (line.startsWith("T="))
						T = Double.parseDouble(line.substring(2));
					else if (line.startsWith("r="))
						r = Double.parseDouble(line.substring(2));
					else if (line.startsWith("b="))
						b = Double.parseDouble(line.substring(2));
					else if (line.startsWith("v="))
						v = Double.parseDouble(line.substring(2));
					else if (line.startsWith("nSteps="))
						nSteps = Integer.parseInt(line.substring(7));
					else if (line.startsWith("nSimulations="))
						nSimulations = Integer.parseInt(line.substring(13));
					else
						System.out.println("ERROR: Unrecognized value in "
								+ "setup file at line: \"" + line + "\".");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (nThreads <= 0) {
			double blockingCoefficient = 0.1;
			int numberOfCores = Runtime.getRuntime().availableProcessors();
			this.nThreads = (int) (numberOfCores / (1 - blockingCoefficient));
		} else
			this.nThreads = nThreads;

		dt = T / nSteps;

		Drift = (b - v * v / 2) * dt;
		vSqrdt = v * Math.sqrt(dt);
		if (CallPutFlag.contentEquals("c"))
			z = 1;
		else if (CallPutFlag.contentEquals("p"))
			z = -1;
		else
			System.out.println("ERROR: \"" + CallPutFlag
					+ "\" is not a valid CallPutFlag !");
	}

	/**
	 * Compute the MonteCarlo estimation with current options Handle the
	 * multi-thread.
	 * 
	 * @return The estimated value
	 */
	public Double compute() {

		TimeTaken = System.currentTimeMillis();
		List<Thread> t = new ArrayList<Thread>();

		for (Integer i = 1; i <= this.nThreads; i++) {
			t.add(new Thread(this, "Thread n° " + i));
			t.get(i - 1).start();
		}

		try {
			for (Thread i : t)
				i.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		Double result = Math.exp(-r * T) * (Sum / nSimulations);
		TimeTaken = System.currentTimeMillis() - TimeTaken;
		return result;
	}

	/**
	 * From Runnable Interface: function triggered by the creation of a new
	 * thread
	 */
	public void run() {
		CalculSimulation(nSimulations / nThreads);
	}

	/**
	 * Function doing the hardest computation part. Can be multi-threaded
	 * 
	 * @param nSimulations
	 *            Number of simulations that has to be done
	 */
	private void CalculSimulation(Integer nSimulations) {
		long start = System.currentTimeMillis();
		Double Sum = 0.0, St = 0.0;
		Random rnd = new Random();
		Integer i = 0, j = 0;

		for (i = 1; i <= nSimulations; i++) {
			St = S;
			for (j = 1; j <= nSteps; j++) {
				St = St * Math.exp(Drift + vSqrdt * rnd.nextGaussian());
			}
			Sum = Sum + Math.max(z * (St - X), 0);
		}

		addToSum(Sum);
		long end = System.currentTimeMillis();
		//System.out.println("Time taken by " + Thread.currentThread().getName()
			//	+ " = " + (end - start));
	}

	/**
	 * Secure function refreshing the value of Sum. Avoid concurrent access for
	 * multithread.
	 * 
	 * @param sum
	 *            The sum to add to the previous one (initialized to 0 at first)
	 */
	private synchronized void addToSum(Double sum) {
		Sum += sum;
	}

	/**
	 * Return the time taken by the compute function to accomplish the
	 * computation
	 * 
	 * @return The time taken.
	 */
	public long getTime() {
		return TimeTaken;
	}

	/**
	 * Main function starting the program (Use for debug here)
	 * 
	 * @param args
	 *            Command line arguments (not used here)
	 */
	public static void main(String args[]) {
		
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
            	Interface mc = new Interface();
            	mc.setName("Monte Carlo");
        		mc.setSize(500, 400);
        		mc.setVisible(true);
            }
        });
	}
}